
package com.trydofor.id.mina;

import java.util.HashMap;
import java.util.Map;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.LoggerFactory;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.byta.Bytable;
import com.trydofor.id.byta.Bytes;
import com.trydofor.id.byta.Head;
import com.trydofor.id.byta.Packet;
import com.trydofor.id.logs.Eventable;
import com.trydofor.id.logs.Eventer;
import com.trydofor.id.logs.LogLevel;

/**
 * thread safe packet decoder.
 * 
 * @author : Shi Rongjiu (www.trydofor.com)
 */
public class PacketDecoder extends CumulativeProtocolDecoder implements Eventable {
    
    /**
     * the events
     */
    public enum Event implements Eventer.Event {
        /**
         * added a new decoder
         */
        DECODER_ADDEDNEW,
        /**
         * replaced a old decoder with new one
         */
        DECODER_REPLACED,

        /**
         * drop 1 bad byte when decoding
         */
        PARSING_ABADBYTE,

        /**
         * can not find decoder for the packet
         */
        PARSING_NODECODER,

        /**
         * can not parse the packet with decoder
         */
        PARSING_UNPARSED
    };
    
    private static final String            HEADER   = PacketDecoder.class.getCanonicalName() + ".HEADER";
    
    private final Eventer                  eventer  = new Eventer(
                                                            LoggerFactory.getLogger(PacketDecoder.class));
    private final Map<Integer, Bytable<?>> decoders = new HashMap<Integer, Bytable<?>>();
    
    /**
     * constructor
     */
    public PacketDecoder() {
        // set default event log level
        eventer.register(Event.DECODER_ADDEDNEW, LogLevel.INFO);
        eventer.register(Event.DECODER_REPLACED, LogLevel.WARN);
        eventer.register(Event.PARSING_ABADBYTE, LogLevel.WARN);
        eventer.register(Event.PARSING_NODECODER, LogLevel.WARN);
        eventer.register(Event.PARSING_UNPARSED, LogLevel.WARN);
    }
    
    /**
     * {@inheritDoc}
     */
    public Eventer getEventer() {
        return eventer;
    }
    
    /**
     * register the decoder.
     * 
     * @param body the body
     * @throws NullPointerException if body if null.
     * @return old decoder if existed.
     */
    public Bytable<?> register(Bytable<?> body) {
        Exceptions.checkNullPointer(body);
        Bytable<?> old = decoders.put(Bytes.classType(body.getClass()), body);
        
        if (old == null) {
            eventer.log(Event.DECODER_ADDEDNEW, "added  a new decoder, class={}", body.getClass()
                                                                                      .getCanonicalName());
        }
        else {
            eventer.log(Event.DECODER_REPLACED, "replaced the decoder, class={}", body.getClass()
                                                                                      .getCanonicalName());
        }
        return old;
    }
    
    /**
     * {@inheritDoc}
     */
    protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
        
        Head head = (Head) session.getAttribute(HEADER); // throw
        
        try {
            // head
            while (head == null) {
                if (in.remaining() < Head.BYTE_SIZE) {
                    return false;
                }
                
                while (in.remaining() >= Head.BYTE_SIZE) {
                    int oldpos = in.position();
                    long token = in.getLong();
                    
                    if (token == Head.FIXED_TOKEN) {
                        long dealCode = in.getLong();
                        int workerId = in.getInt();
                        int bodyType = in.getInt();
                        int bodySize = in.getInt();
                        
                        head = new Head(workerId, dealCode);
                        head.setBodyType(bodyType);
                        head.setBodySize(bodySize);
                        
                        session.setAttribute(HEADER, head);
                        break;
                    }
                    else {
                        in.position(oldpos);
                        byte b = in.get();
                        eventer.log(Event.PARSING_ABADBYTE, "Drop 1 bad byte[{}] at {}", b, oldpos);
                    }
                }
            }
            
            // body
            if (in.remaining() >= head.getBodySize()) {
                byte[] bytes = new byte[head.getBodySize()];
                in.get(bytes);
                
                Bytable<?> decoder = decoders.get(head.getBodyType());
                if (decoder == null) {
                    eventer.log(Event.PARSING_NODECODER, "Drop an undecodable packet, head:{}", head);
                }
                else {
                    Bytable<?> body = decoder.ofBytes(bytes, 0, bytes.length);
                    if (body == null) {
                        eventer.log(Event.PARSING_UNPARSED, "Drop an unparsed packet, head:{}", head);
                    }
                    else {
                        Packet packet = new Packet(head, body);
                        out.write(packet);
                    }
                }
                session.removeAttribute(HEADER); // remove/done
                return true;
            }
            else {
                return false;
            }
        }
        catch (Exception e) {
            session.removeAttribute(HEADER); // remove/exception
            throw e;
        }
    }
}
